﻿using Spire.Pdf;
using Spire.Pdf.Graphics;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;


namespace SuperKit
{
    class PDFShrink : CommandExecutor {
        public const string SAVE_DIR = "(COMPRESS)";
        private int width { get; }
        private int height { get; }
        private int quality { get; }
        private bool compress { get; }
        // 当为false的时候，如果目标已经存在，则不进行处理了。如果为true，则会用新的覆盖。
        private bool isOverride;
        // 使用非线程的方式进行。如果文件非常非常大（几个G），那么读取的时候可能因为线程导致内存的问题，所以使用非线程模式进行。
        private bool nonThread;

        private class PDFShrinkItem : ThreadToolItem {
            public FileInfo file { get; }
            public PDFShrink Parent { get; }

            public PDFShrinkItem(FileInfo file, PDFShrink parent) {
                this.file = file;
                Parent = parent;
            }

            public override void Process() {
                Parent.Process(this);
            }

            public override string ToString() {
                return file.Name;
            }
        }


        public PDFShrink(ArgReader arg, IExecutorOwner form, string fpath) : base(arg, form, fpath) {
            width = arg.GetInteger("-width", 2560);
            height = arg.GetInteger("-height", 2560);
            quality = arg.GetInteger("-quality", 80);
            compress = arg.IsContain("-compress");
            isOverride = arg.IsContain("-override");
            nonThread = arg.IsContain("-nonThread");
        }

        public List<FileInfo> ProcessList { get; private set; }
        public bool IsStreamError { get; private set; }

        private string containDir;
        private ThreadTool<PDFShrinkItem> threadList;

        public override void Execute() {
            base.Execute();
            ProcessList = new List<FileInfo>();

            var dir = new DirectoryInfo(FilePath);
            GetAllFiles(dir, ProcessList, Kit.PDF_EXTS);

            if (Kit.IsEmpty(TargetDir)) {
                containDir = Path.Combine(FilePath, SAVE_DIR);
            } else {
                containDir = Path.Combine(TargetDir, dir.Name);
            }
            

            threadList = new ThreadTool<PDFShrinkItem>(MaxThread, SetStatus);
            foreach (FileInfo file in ProcessList) {
                threadList.Add(new PDFShrinkItem(file, this));
            }

            if (nonThread) {
                threadList.StartNonThread();
                End();
            } else {
                Thread t = new Thread(StartClear);
                t.Start();
            }            
        }

        private void StartClear() {
            threadList.Start();
            End();
        }

        private string GetTargetPath(FileInfo file) {
            // 本地的路径
            string extraPath = file.FullName.Substring(FilePath.Length + 1);
            string path = Path.Combine(containDir, extraPath);
            string dir = Path.GetDirectoryName(path);
            if (Directory.Exists(dir) == false) {  
                Directory.CreateDirectory(dir);
            }
            return path;
        }


        private void Process(PDFShrinkItem item) {
            FileInfo file = item.file;
            var path = GetTargetPath(file);

            if (File.Exists(path)) {
                if (isOverride == false) {
                    Flush($"目标已经存在：{path}");
                    return;
                }
            }

            int changed = 0;
            int passed = 0;
            PdfDocument doc = new PdfDocument(file.FullName);
            PdfDocument newDoc = new PdfDocument();

            int index = 0;
            foreach (PdfPageBase i in doc.Pages) {
                if (Shrink(newDoc, i, index++)) {
                    changed++;
                } else {
                    passed++;
                }
                
            }
            newDoc.SaveToFile(path);

            doc.Close();
            newDoc.Close();

            var newFile = new FileInfo(path);
            var msg = string.Format("完成({0}). {1:N1}mb => {2:N1}mb.", file.Name, item.file.Length / Kit.MB, newFile.Length / Kit.MB);
            Flush(msg);
        }


        private bool Shrink(PdfDocument doc, PdfPageBase page, int index) {
            var imgs = page.ExtractImages();
            if (imgs == null || imgs.Length == 0) {
                return false;
            }

            var size = Kit.GetResize(imgs[0], width, height);

            Bitmap bmp = new Bitmap(size.Width, size.Height);
            Graphics g = Graphics.FromImage(bmp);
            g.DrawImage(imgs[0], 0, 0, size.Width, size.Height);


            MemoryStream ms = SaveToSteam(bmp);
            var img = PdfImage.FromStream(ms);

            var newPage = doc.Pages.Insert(index, size, new PdfMargins(0f));
            newPage.Canvas.DrawImage(img, new PointF(0f, 0f), size);

            if (compress) {                
                newPage.TryCompressImage(0);
            }            

            bmp.Dispose();
            g.Dispose();
            ms.Dispose();

            foreach (var i in imgs) {
                i.Dispose();
            }

            return true;
        }

        private MemoryStream SaveToSteam(Image bitmap) {
            MemoryStream stream = new MemoryStream();
            IsStreamError = false;

            ImageCodecInfo jpgEncoder = Kit.GetEncoder(ImageFormat.Jpeg);
            System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality;
            EncoderParameters myEncoderParameters = new EncoderParameters(1);
            EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, quality);
            myEncoderParameters.Param[0] = myEncoderParameter;

            try {
                bitmap.Save(stream, jpgEncoder, myEncoderParameters);
            } catch (ExternalException) {
                IsStreamError = true;
            }
            return stream;

        }


    }
}
